import { Injectable, NgZone, Renderer2, RendererFactory2 } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { auditTime } from 'rxjs/operators';

interface DomListener {
  handler: (e: Event) => void;
  unsubscribe?: () => void;
  // tslint:disable-next-line:member-ordering
  countOfListeners: number;
}

@Injectable({
  providedIn: 'root',
})
export class DomEventService {
  private renderer: Renderer2;
  private readonly resizeSource$ = new Subject<void>();
  private readonly domEventListeners = new Map<string, DomListener>();

  constructor(
    private ngZone: NgZone,
    private rendererFactory2: RendererFactory2,
  ) {
    this.renderer = this.rendererFactory2.createRenderer(null, null);
  }

  unregisterResizeListener(): void {
    if (this.domEventListeners.has('resize')) {
      const listener = this.domEventListeners.get('resize');
      this.tryToStopListener(listener);
    }
  }

  registerResizeListener(): Observable<void> {
    if (!this.domEventListeners.has('resize')) {
      this.domEventListeners.set('resize', {
        handler: (): void => {
          this.resizeSource$.next();
        },
        countOfListeners: 0,
      });
    }

    const listener = this.domEventListeners.get('resize');
    this.tryToStartListener(listener, 'resize');

    return this.resizeSource$.pipe(auditTime(16));
  }

  private tryToStopListener(listener: DomListener): void {
    listener.countOfListeners -= 1;
    if (listener.countOfListeners === 0) {
      listener.unsubscribe();
      listener.unsubscribe = undefined;
    }
  }

  private tryToStartListener(listener: DomListener, name: string): void {
    listener.countOfListeners += 1;
    this.ngZone.runOutsideAngular((): void => {
      if (listener.countOfListeners === 1) {
        listener.unsubscribe = this.renderer.listen('window', name, listener.handler);
      }
    });
  }
}
